Interpreter Sniffing by Friends of I7

Version 1/140209 (for Glulx only)

"Phrases for testing, as far as is possible, which interpreter the story is running under."
Jump to extension code
Jump to "Fourth Wall" example
Copy Include Interpreter Sniffing by Friends of I7 to clipboard Include Interpreter Sniffing by Friends of I7.
With Interpreter Sniffing included we can determine the interpreter the story is
running under by writing

the current interpreter

as in

if the current interpreter is Zoom:
....

At present, the possibilities are

FyreVM

Zoom

Git/Gift-based interpreter

and

Unknown interpreter

They all have the kind

an interpreter

For more detail, we can ask about

the current Glulx implementation

which has kind

a Glulx implementation

and the possibilities

Git/Gift Glulx

FyreVM Glulx

and

Unknown Glulx implementation

Likewise, we may wish to know

the current IO implementation

which has the kind

an IO implementation

It may be

FyreVM channel system

CocoaGlk

Unknown Glk implementation

or

Unknown non-Glk implementation

All of these tests have associated phrases to determine corresponding version
numbers:

the current interpreter version number

the current Glulx version number

and

the current IO version number

Each of these decides on a value with the kind

a version number

which is an arithmetic kind of value written in three parts, major version,
minor version, and patch level, separated by dots. For example,

3.1.2

has major version 3, minor version 1, and patch level 2.

With this syntax we can write conditionals like

if the current Glulx version number is at least 3.1.2:
....

Finally, it is possible for the interpreter to change mid-story if the player
saves and restores. If the change can be detected, rules in

the when the interpreter changes rulebook

will be run; these are written

When the interpreter changes:
....

Note that these rules are run as soon as possible, and the story might not have
regained its bearings yet. (In particular, no Glk recovery has happened.) It
is generally not safe to produce output within such rules; we should instead
note the differences and respond at a less precarious time, such as before the
next command prompt.

Example: * Fourth Wall - Interpreter information in the story banner and other parts of the story.

Copy "Fourth Wall" to clipboard
"Fourth Wall"

Include Interpreter Sniffing by Friends of I7.

After printing the banner text:
   say "[The current interpreter] [the current interpreter version number][line break]".

Terp is a room.
"[A lonely wooden sign] swings from an otherwise bare ceiling, and three of the walls are similarly blank. Through [the fourth wall], however, you can vaguely make out [the author]."

The ceiling is scenery in the terp.
The description is "Blank, unstoried."

Some blank walls are scenery in the terp.
The description is "Blank, unstoried."

A lonely wooden sign is scenery in the terp.
The description is "The sign reads '[The current Glulx implementation] [the current Glulx version number]'."

The fourth wall is scenery in the terp.
Understand "etched" and "surface" as the fourth wall.
The description of the fourth wall is "Etched on the surface is the text, '[The current IO implementation] [the current IO version number]'."
Instead of searching the fourth wall:
   try examining the author.

The author is a scenery person in the terp.
The description is "As best you can tell, [the author] is [the author's name]."

Include (-
   #ifdef Story_Author;
     Constant Author_Name = Story_Author;
   #ifnot;
     Constant Author_Name = (+ "Anonymous" +);
   #endif;
-) after "Definitions.i6t".
To decide what text is the author's name: (- Author_Name -).

Interpreter changed is a truth state that varies; interpreter changed is false.
When the interpreter changes:
   now interpreter changed is true.
Before reading a command when interpreter changed is true:
   say "Whoa, déjà vu. (It happens when they change something.)";
   now interpreter changed is false.

Test me with "x sign / x wall / x author".
Version 1/140209 of Interpreter Sniffing (for Glulx only) by Friends of I7 begins here.

"Phrases for testing, as far as is possible, which interpreter the story is running under."

"with contributions by Dannii Willis and Brady Garvin"



Book "Kinds for Describing Interpreters"

Chapter "Glulx Implementations"

A Glulx implementation is a kind of value.
Unknown Glulx implementation is a Glulx implementation.

Chapter "IO Implementations"

An IO implementation is a kind of value.
Unknown Glk implementation and Unknown non-Glk implementation are IO implementations.

Chapter "Interpreters"

An interpreter is a kind of value.
Unknown interpreter is an interpreter.

Chapter "Version Numbers"

A version number is a kind of value.
65535.255.255 specifies a version number.

Section "Support Phrases for Saying Version Numbers without Leading Zeros" (unindexed)

Include (-
   [ is_logicalRightShift value distance;
     @ushiftr value distance sp;
     @return sp;
   ];
-).

To decide what number is the bitwise and of (I - an arithmetic value) and (J - a number): (- ({I} & {J}) -).
To decide what number is (I - an arithmetic value) logically shifted (D - a number) bit/bits right: (- is_logicalRightShift({I}, {D}) -).

Section "Saying Version Numbers without Leading Zeros"

[This override does not affect showmes, but extra zeros in debugging output give no great harm.]

To say (N - a version number):
   let the major version number be N logically shifted 16 bits right;
   let the minor version number be the bitwise and of N logically shifted 8 bits right and 255;
   let the patch level be the bitwise and of N and 255;
   say "[the major version number].[no line break][the minor version number].[no line break][the patch level]".

Book "Framework for Detecting Interpreters"

Chapter "Workarounds" (unindexed)

[For Inform bug 759.]
To consider (R - a nothing based rule): (- FollowRulebook({R}); -).
To decide what K is the (name of kind K) produced by (R - a nothing based rule producing a value of kind K): (- ResultOfRule({R}, 0, true, {-strong-kind:K}) -).

Chapter "Responding to Restores" (unindexed)

[A player can save a story under one interpreter and restore under another; we need to resniff (and possibly notify the story) after any restore.]

The resniffing rulebook is a nothing based rulebook.
The when the interpreter changes rulebook is a nothing based rulebook.

To resniff the interpreter after a restore (this is resniffing the interpreter after a restore):
   consider the resniffing rulebook;
   let the old result of Glulx implementation detection be a Glulx implementation;
   let the new result of Glulx implementation detection be a Glulx implementation;
   let the old result of IO implementation detection be an IO implementation;
   let the new result of IO implementation detection be an IO implementation;
   if Glulx implementation already detected is true:
     now the old result of Glulx implementation detection is the cached result of Glulx implementation detection;
     now Glulx implementation already detected is false;
     now the new result of Glulx implementation detection is the current Glulx implementation;
   if IO implementation already detected is true:
     now the old result of IO implementation detection is the cached result of IO implementation detection;
     now IO implementation already detected is false;
     now the new result of IO implementation detection is the current IO implementation;
   unless the old result of Glulx implementation detection is the new result of Glulx implementation detection and the old result of IO implementation detection is the new result of IO implementation detection:
     consider the when the interpreter changes rulebook.

Section "Initial Resniffing" (unindexed)

This is the resniffing stage rule:
   consider the resniffing rulebook.
The resniffing stage rule is listed before the virtual machine startup rule in the startup rulebook.

Section "Injection of Post-Restore Resniffing" (unindexed)

Include (-
   [ SAVE_THE_GAME_R res fref;
     if (actor ~= player) rfalse;
     fref = glk_fileref_create_by_prompt($01, $01, 0);
     if (fref == 0) jump SFailed;
     gg_savestr = glk_stream_open_file(fref, $01, GG_SAVESTR_ROCK);
     glk_fileref_destroy(fref);
     if (gg_savestr == 0) jump SFailed;
     @save gg_savestr res;
     if (res == -1) {
       ! The player actually just typed "restore". We're going to print
       ! GL__M(##Restore,2); the Z-Code Inform library does this correctly
       ! now. But first, we have to recover all the Glk objects; the values
       ! in our global variables are all wrong.
       ((+ resniffing the interpreter after a restore +)-->1)();
       GGRecoverObjects();
       glk_stream_close(gg_savestr, 0); ! stream_close
       gg_savestr = 0;
       RESTORE_THE_GAME_RM('B'); new_line;
       rtrue;
     }
     glk_stream_close(gg_savestr, 0); ! stream_close
     gg_savestr = 0;
     if (res == 0) { SAVE_THE_GAME_RM('B'); new_line; rtrue; }
     .SFailed;
     SAVE_THE_GAME_RM('A'); new_line;
   ];
-) instead of "Save The Game Rule" in "Glulx.i6t".

Chapter "Detecting Glulx Implementations"

Section "Cached Glulx Implementation Detection" (unindexed)

Glulx implementation already detected is a truth state that varies. Glulx implementation already detected is false.
The cached result of Glulx implementation detection is a Glulx implementation that varies.

Chapter "Rulebook and Phrase for Detecting Glulx Implementations"

The Glulx implementation detection rulebook is a nothing based rulebook producing a Glulx implementation.

To decide what Glulx implementation is the current Glulx implementation:
   if Glulx implementation already detected is false:
     now the cached result of Glulx implementation detection is the Glulx implementation produced by the Glulx implementation detection rulebook;
     unless the rule succeeded:
       now the cached result of Glulx implementation detection is Unknown Glulx implementation;
     now Glulx implementation already detected is true;
   decide on the cached result of Glulx implementation detection.

Chapter "Detecting Glulx Versions"

Include (-
   [ is_glulxVersion;
     @gestalt 0 0 sp;
     @return sp;
   ];
-).

To decide what version number is the current Glulx version number: (- is_glulxVersion() -).

Chapter "Detecting IO Implementations"

Section "Cached IO Implementation Detection" (unindexed)

IO implementation already detected is a truth state that varies. IO implementation already detected is false.
The cached result of IO implementation detection is an IO implementation that varies.

Section "Glk Test" (unindexed)

Include (-
   [ is_Glk;
     @gestalt 4 2 sp;
     @return sp;
   ];
-).
To decide whether the Glk gestalt is set: (- is_Glk() -).

Chapter "Rulebook and Phrase for Detecting IO Implementations"

The IO implementation detection rulebook is a nothing based rulebook producing an IO implementation.

To decide what IO implementation is the current IO implementation:
   if IO implementation already detected is false:
     now the cached result of IO implementation detection is the IO implementation produced by the IO implementation detection rulebook;
     unless the rule succeeded:
       if the Glk gestalt is set:
         now the cached result of IO implementation detection is Unknown Glk implementation;
       otherwise:
         now the cached result of IO implementation detection is Unknown non-Glk implementation;
     now IO implementation already detected is true;
   decide on the cached result of IO implementation detection.

Chapter "Detecting IO Versions"

Include (-
   [ is_ioVersion
     canGetVersion;
     @gestalt 4 2 canGetVersion;
     if (~~canGetVersion) {
       return 0;
     }
     return glk_gestalt(gestalt_Version, 0);
   ];
-).

To decide what version number is the current IO version number: (- is_ioVersion() -).

Chapter "Detecting Interpreter Versions"

Include (-
   [ is_interpreterVersion;
     @gestalt 1 0 sp;
     @return sp;
   ];
-).

To decide what version number is the current interpreter version number: (- is_interpreterVersion() -).

Book "Glulx Implementation Tests"

Chapter "Git/Gift"

Git/Gift Glulx is a Glulx implementation.

Section "Git/Gift Test" (unindexed)

Include (-
   [ is_GitGift;
     @gestalt 31040 0 sp;
     @return sp;
   ];
-).
To decide whether the Git gestalt is set: (- is_GitGift() -).

Section "Git/Gift Rule" (unindexed)

Glulx implementation detection (this is the test for Git/Gift rule):
   if the Git gestalt is set:
     rule succeeds with result Git/Gift Glulx.

Chapter "FyreVM"

FyreVM Glulx is a Glulx implementation.

Section "FyreVM Test" (unindexed)

Include (-
   [ is_FyreVM;
     @gestalt 4 20 sp;
     @return sp;
   ];
-).
To decide whether the FyreVM gestalt is set: (- is_FyreVM() -).

Section "FyreVM Rule" (unindexed)

Glulx implementation detection (this is the test for FyreVM rule):
   if the FyreVM gestalt is set:
     rule succeeds with result FyreVM Glulx.

Book "IO Implementation Tests"

Chapter "FyreVM Channel System"

The FyreVM channel system is an IO implementation.

Section "FyreVM Channel System Test" (unindexed)

[See Section "FyreVM Test" above.]

Section "FyreVM Channel System Rule" (unindexed)

IO implementation detection (this is the test for the FyreVM channel system rule):
   if the FyreVM gestalt is set:
     rule succeeds with result FyreVM channel system.

Chapter "CocoaGlk"

CocoaGlk is an IO implementation.

Section "CocoaGlk Test" (unindexed)

The CocoaGlk detection flag is a truth state that varies. the CocoaGlk detection flag is false.

Include (-
   Array is_cocoaMethodCheck --> 1;
   Array is_cocoaSizeCheck --> 1;
   Array is_cocoaKeyWindowCheck --> 1;
   [ is_detectCocoaGlk
     glkSupported root nonroot firstWindow secondWindow;
     ! Detect CocoaGlk via Inform bug 819, without falling afoul of Inform bug 961.
     (+ CocoaGlk detection flag +) = false;
     ! If there is no Glk, there is obviously no CocoaGlk, so rule out that case first.
     @gestalt 4 2 glkSupported;
     if (~~glkSupported) {
       rfalse;
     }
     ! Now, to identify bug 819 we need two related windows whose circumstances of creation we know about.
     ! (So that we can be sure that one should be the key window of the other.)
     ! We do not want to rely on anything the story has done previously, so we will create these windows ourselves and destroy them later.
     ! Windows are created by establishing a root or by splitting, depending on whether a root already exists.
     ! Therefore, test for a root.
     root = glk_window_get_root();
     if (root) {
       ! A root already exists, so we must split.
       ! Under 961, closing a window split off of the root without also closing the root can make the story invisible.
       ! Consequently, we cannot safely split the root itself.
       ! So we look for a nonroot window.
       for (nonroot = 0: nonroot = glk_window_iterate(nonroot, 0):) {
         if (nonroot ~= root) {
           break;
         }
       }
       ! If we found a window to split, the loop terminated by a break and set nonroot to that window.
       ! Otherwise nonroot is zero, from the end of the glk_window_iterate cycle.
       if (nonroot) {
         ! Since we found a window, split it.
         firstWindow = glk_window_open(nonroot, winmethod_Below | winmethod_Proportional, 50, wintype_TextBuffer, 0);
       } else {
         ! Otherwise we know that there is exactly one window open.
         ! Rather than risk bug 961, we opt to close this window and create a root.
         ! The story's Glk recovery code should be able to handle that reasonably.
         glk_window_close(root, 0);
         firstWindow = glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
       }
     } else {
       ! A root does not exist, so we may create one.
       firstWindow = glk_window_open(0, 0, 0, wintype_TextBuffer, 0);
     }
     ! Root creation and window splitting may fail under some Glk implementations, but not CocoaGlk.
     ! If we saw a failure, we can skip the rest of this detection process.
     if (firstWindow) {
       ! Now there must be a root, and we are sure that firstWindow is safe to split.
       ! Split it.
       secondWindow = glk_window_open(firstWindow, winmethod_Below | winmethod_Proportional, 50, wintype_TextBuffer, 0);
       ! Again, window splitting may fail under some Glk implementations, but not CocoaGlk.
       ! If we saw a failure, we can skip the rest of this detection process, except for cleaning up the window we did create.
       if (secondWindow) {
         ! Finally, we have our two windows whose circumstances of creation we know about.
         ! We ask the Glk implementation for the key of firstWindow, expecting it to be secondWindow.
         glk_window_get_arrangement(glk_window_get_parent(firstWindow), is_cocoaMethodCheck, is_cocoaSizeCheck, is_cocoaKeyWindowCheck);
         ! If, instead, we got a result of firstWindow, that's bug 819, and we're dealing with CocoaGlk.
         (+ CocoaGlk detection flag +) = (is_cocoaKeyWindowCheck-->0) == firstWindow;
         ! Now clean up the second window.
         glk_window_close(secondWindow, 0);
       }
       ! And clean up the first window.
       glk_window_close(firstWindow, 0);
     }
     rfalse;
   ];
-).

The detect CocoaGlk rule translates into I6 as "is_detectCocoaGlk".
The detect CocoaGlk rule is listed in the resniffing rulebook.

Section "CocoaGlk Rule" (unindexed)

IO implementation detection (this is the test for CocoaGlk rule):
   if the CocoaGlk detection flag is true:
     rule succeeds with result CocoaGlk.

Book "Interpreter Tests"

FyreVM, Zoom, and Git/Gift-based interpreter are interpreters.

To decide what interpreter is the current interpreter:
   if the current IO implementation is:
     -- FyreVM channel system:
       decide on FyreVM;
     -- CocoaGlk:
       decide on Zoom; [This is potentially controversial, as the I7 Mac IDE also uses CocoaGlk.]
     -- Unknown Glk implementation:
       if the current Glulx implementation is:
         -- Git/Gift Glulx:
           decide on Git/Gift-based interpreter;
   decide on Unknown interpreter.

Interpreter Sniffing ends here.